package com.sh.data.engine.application.service.integration;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.sh.data.engine.application.model.dto.base.PagerResponse;
import com.sh.data.engine.application.model.dto.common.DirectoryResponse;
import com.sh.data.engine.application.model.dto.integration.*;
import com.sh.data.engine.application.model.dto.workspace.UserResponse;
import com.sh.data.engine.application.model.mapper.integration.OfflineSyncPageResponsePageMapper;
import com.sh.data.engine.application.model.mapper.integration.OfflineSyncRunningRecordResponsePageMapper;
import com.sh.data.engine.application.model.request.common.UpsertDirectoryRequest;
import com.sh.data.engine.application.model.request.integration.CreateSyncTaskRequest;
import com.sh.data.engine.application.model.request.integration.OfflineSyncRequest;
import com.sh.data.engine.application.model.request.integration.OfflineTaskExistTableMappingRequest;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.common.util.ServletUtils;
import com.sh.data.engine.domain.base.model.PageResult;
import com.sh.data.engine.domain.base.model.enums.DirectoryMenuEnum;
import com.sh.data.engine.domain.common.model.domain.DirectoryDomain;
import com.sh.data.engine.domain.common.model.param.UpsertDirectoryParam;
import com.sh.data.engine.domain.common.service.DirectoryService;
import com.sh.data.engine.domain.dataasset.service.TblPropService;
import com.sh.data.engine.domain.integration.datasource.model.domain.DataSourceCreateTableDomain;
import com.sh.data.engine.domain.integration.datasource.model.domain.DataSourceDomain;
import com.sh.data.engine.domain.integration.datasource.model.domain.DataSourceManagerDomain;
import com.sh.data.engine.domain.integration.datasource.service.DataSourceService;
import com.sh.data.engine.domain.integration.offline.model.domain.*;
import com.sh.data.engine.domain.integration.offline.service.OfflineSyncService;
import com.sh.data.engine.domain.normalization.modeldesign.model.model.param.SaveModelParam;
import com.sh.data.engine.domain.normalization.modeldesign.model.service.ModelService;
import com.sh.data.engine.domain.shims.db.model.FieldInfoDomain;
import com.sh.data.engine.domain.shims.db.model.PreviewDataDomain;
import com.sh.data.engine.domain.shims.db.model.TableInfoDomain;
import com.sh.data.engine.domain.util.ConvertUtil;
import com.sh.data.engine.domain.workspace.manager.model.domain.UserDomain;
import com.sh.data.engine.domain.workspace.manager.service.UserService;
import com.sh.data.engine.repository.dao.dataasset.entity.TblPropEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author: mengzheng.mhc
 * @date: 2024/7/1 12:43
 */
@Slf4j
@Service
public class OfflineSyncApplicationService {

    @Autowired
    private OfflineSyncService offlineTaskService;

    @Autowired
    private DataSourceService dataSourceService;

    @Autowired
    private DirectoryService directoryService;

    @Autowired
    private UserService userService;

    @Autowired
    private ModelService modelService;

    //    @Autowired
//    private SecretMgtService secretMgtService;

    @Autowired
    private TblPropService tblPropService;

    @Autowired
    private OfflineSyncPageResponsePageMapper offlineSyncPageResponsePageMapper;

    @Autowired
    private OfflineSyncRunningRecordResponsePageMapper offlineSyncRunningRecordResponsePageMapper;

    public List<OfflineSyncDatabaseResponse> getOfflineSyncDatabases(Integer direction, String dsId) {
        List<OfflineSyncDatabaseDomain> offlineTaskDatabaseDomains =
            offlineTaskService.getOfflineSyncDatabases(direction, dsId);
        return ConvertUtil.copyProperties(offlineTaskDatabaseDomains, OfflineSyncDatabaseResponse.class);
    }

    public List<OfflineSyncTablesResponse> getOfflineSyncTables(Integer direction, Long dsId, String schema) {
        List<OfflineSyncTableDomain> datas =
            offlineTaskService.getOfflineSyncTables(direction, dsId, schema);
        datas.forEach(
            data -> {
                if (data.getDetailedTblName() == null) {
                    data.setDetailedTblName("");
                }
            });
        return Convert.toList(OfflineSyncTablesResponse.class, datas);
    }

    public List<String> getSchemas(Long dsId, String dsType) {
        return dataSourceService.getDataSourceSchema(dsId, dsType);
    }

    public String getCreateTableSql(Long dsId, String tableName, Long targetDsId, String schema, int infoCase) {
        return dataSourceService.getCreateTableSql(dsId, tableName, targetDsId, schema, infoCase);
    }

    public OfflineSyncCreateTableResponse executeCreateTableSql(Long dsId, String scripts, Long dataSecretId) {
        // 设置了密级
        boolean requireSetDataSecret = Objects.nonNull(dataSecretId);
        long secretId = -1;
        if (requireSetDataSecret) {
//            secretId = Long.valueOf(dataSecretId);
//            Long securityLevelId = UserContext.getUserInfo().getSecurityLevelId();
//            log.info("user secretId: {}, data secretId: {}", securityLevelId, secretId);
//            // 判断当前用户密级
//            if (!secretMgtService.validSecretLevel(securityLevelId, secretId)) {
//                return RestResponseEntity.error("数据密级设置异常");
//            }
        }
        DataSourceCreateTableDomain dataSourceCreateTableDomain =
            dataSourceService.executeCreateTableSql(dsId, scripts);
        OfflineSyncCreateTableResponse offlineSyncCreateTableResponse =
            ConvertUtil.copyProperties(
                dataSourceCreateTableDomain, OfflineSyncCreateTableResponse.class);

        Long projectId = ServletUtils.getProjectId();

        // 保存tblProp
        TblPropEntity tblPropEntity = new TblPropEntity();
        tblPropEntity.setProjectId(projectId);
        tblPropEntity.setSchema(dataSourceCreateTableDomain.getSchema());
        tblPropEntity.setTblId(tblPropService.generateTblId(projectId));
        tblPropEntity.setDataSecretId(secretId);
        tblPropEntity.setTableName(dataSourceCreateTableDomain.getOnlyTableName());
        tblPropEntity.setLifecycle(-1);
        tblPropEntity.setTblType(TableInfoDomain.TblType.MANAGED_TABLE.name());
        tblPropEntity.setRowState(1);
        tblPropService.save(tblPropEntity);

        return offlineSyncCreateTableResponse;
    }

    public OfflineTaskExistTableMappingResponse getExistTablesMappings(OfflineTaskExistTableMappingRequest request) {
        OfflineTaskExistTableMappingQueryDomain queryDomain =
            Convert.convert(OfflineTaskExistTableMappingQueryDomain.class, request);
        OfflineTaskExistTableMappingDomain datas =
            this.offlineTaskService.getExistTablesMappings(queryDomain);
        return Convert.convert(OfflineTaskExistTableMappingResponse.class, datas);
    }

    public PreviewDataDomain previewData(Long dsId, String tableName) {
        return dataSourceService.previewData(dsId, tableName);
    }

    public boolean saveOfflineSyncTask(OfflineSyncRequest request) {
        OfflineSyncDomain offlineSyncDomain = Convert.convert(OfflineSyncDomain.class, request);
        offlineTaskService.saveOrUpdateOfflineSyncTask(offlineSyncDomain);
        return true;
    }

    public boolean saveSpOfflineSyncTask(OfflineSyncRequest request) {
        OfflineSyncDomain offlineSyncDomain = Convert.convert(OfflineSyncDomain.class, request);
        offlineSyncDomain.setEdit(Boolean.TRUE);
        offlineTaskService.saveOrUpdateOfflineSyncTask(offlineSyncDomain);
        offlineTaskService.online(request.getId());
        return true;
    }

    public boolean deleteOfflineSyncTask(Long id) {
        return this.offlineTaskService.delOfflineSyncTask(id);
    }

    public OfflineSyncResponse getDetail(Long id) {
        OfflineSyncDomain offlineSyncDomain = offlineTaskService.getOfflineSyncById(id);
        return Convert.convert(OfflineSyncResponse.class, offlineSyncDomain);
    }

    public PagerResponse<OfflineSyncPageResponse> getOfflineSyncTaskPage(
        Integer pageNum, Integer pageSize, String taskName, String taskStatus, Integer taskRunStatus,
        String creatorId, Date startTime, Date endTime, Long dirId, String order, String orderField, String cycle
    ) {
        Long projectId = ServletUtils.getProjectId();
        OfflineSyncTaskQueryDomain offlineSyncPageQueryDomain =
            OfflineSyncTaskQueryDomain.builder()
                .taskName(taskName)
                .taskStatusList(taskStatus)
                .taskRunStatus(taskRunStatus)
                .startTime(startTime)
                .endTime(endTime)
                .pageNum(pageNum)
                .pageSize(pageSize)
                .dirId(dirId)
                .userId(creatorId)
                .order(order)
                .orderField(orderField)
                .cycle(cycle)
                .projectId(projectId)
                .build();
        PageResult<OfflineSyncPageDomain> pageDomain =
            offlineTaskService.getPageList(offlineSyncPageQueryDomain);
        return offlineSyncPageResponsePageMapper.toPageResponse(pageDomain);
    }

    public OfflineSyncStatisticsResponse queryStatistics() {
        OfflineSyncStatisticsDomain statisticsDomain = offlineTaskService.statistics();
        return ConvertUtil.copyProperties(statisticsDomain, OfflineSyncStatisticsResponse.class);
    }

    public PagerResponse<OfflineSyncRunningRecordResponse> historyTask(
        Integer pageNum, Integer pageSize, Long taskId, Integer taskStatus, Integer executeType
    ) {
        OfflineSyncTaskQueryDomain queryDomain =
            OfflineSyncTaskQueryDomain.builder()
                .taskId(taskId)
                .pageNum(pageNum)
                .pageSize(pageSize)
                .taskStatus(taskStatus)
                .executeType(executeType)
                .build();
        PageResult<OfflineSyncRunningRecordDomain> pageDomain =
            offlineTaskService.historyTask(queryDomain);
        return offlineSyncRunningRecordResponsePageMapper.toPageResponse(pageDomain);
    }

    public boolean publishTask(Long taskId) {
        offlineTaskService.online(taskId);
        return true;
    }

    public boolean offlineTask(Long taskId) {
        offlineTaskService.offline(taskId);
        return true;
    }

    public String runTask(Long taskId, Integer runType) {
        return offlineTaskService.run(taskId, runType);
    }

    public OfflineSyncLogResponse getOfflineSyncLog(Long logId, Integer offset) {
        OfflineSyncLogDomain offlineSyncLogDomain = offlineTaskService.getOfflineSyncLog(logId, offset);
        return Convert.convert(OfflineSyncLogResponse.class, offlineSyncLogDomain);
    }

    public boolean stopTask(String jobId) {
        offlineTaskService.stop(jobId);
        return true;
    }

    public List<DirectoryResponse> getDirList(String search) {
        Long projectId = ServletUtils.getProjectId();
        List<DirectoryDomain> dirList =
            directoryService.getDirList(projectId, DirectoryMenuEnum.OFFLINE, search);
        return ConvertUtil.copyProperties(dirList, DirectoryResponse.class);
    }

    public boolean upsetDir(UpsertDirectoryRequest request) {
        UpsertDirectoryParam param = Convert.convert(UpsertDirectoryParam.class, request);
        Long projectId = ServletUtils.getProjectId();
        param.setProjectId(projectId);
        param.setMenuCode(DirectoryMenuEnum.OFFLINE.getCode());

        return directoryService.upsetDir(param);
    }

    public boolean deleteDir(Long id) {
        return offlineTaskService.deleteDir(id);
    }

    public boolean saveAndDoSync(CreateSyncTaskRequest request) {
        Long projectId = request.getProjectId();
        DataSourceDomain systemDataSource = dataSourceService.getSystemDataSourceByProjectId(projectId);
        List<CreateSyncTaskRequest.EngineSyncTask> tasks = request.getTasks();
        String schema = "public";
        String sourceDsType = request.getSourceDsType();
        Long sourceDsId = request.getSourceDsId();
        Long themeId = Optional.ofNullable(request.getThemeId()).orElse(0L);
        DataSourceManagerDomain sourceManagerInfo = dataSourceService.getManagerInfo(sourceDsId);
        DataSourceManagerDomain targetManagerInfo =
            dataSourceService.getManagerInfo(systemDataSource.getId());

        tasks.forEach(
            task -> {
                String sourceTableWithSchema =
                    StrUtil.isBlank(request.getSchema())
                        ? task.getSourceTable()
                        : request.getSchema() + "." + task.getSourceTable();
                String targetTableWithSchema =
                    StrUtil.isBlank(schema)
                        ? task.getTargetTable()
                        : schema + "." + task.getTargetTable();

                // //TODO 待修改
                // String sql =
                //     dataSourceService.getCreateTableSql(
                //         sourceDsId, sourceTableWithSchema, systemDataSource.getId(), schema, 1);
                // if (DSType.PostgreSQL.name().equals(systemDataSource.getDsType())) {
                //   sql = sql.replace("ods_" + task.getSourceTable(), task.getTargetTable());
                //   sql =
                //       sql.concat(
                //           "\nCOMMENT ON TABLE \""
                //               + schema
                //               + "\".\""
                //               + task.getTargetTable()
                //               + "\" IS '"
                //               + task.getTargetTableCN()
                //               + "';");
                // } else {
                //   sql = sql.replace("ods_" + sourceTableWithSchema, targetTableWithSchema);
                //   sql =
                //       sql.substring(0, sql.length() - 1)
                //           + " \nCOMMENT = '"
                //           + task.getTargetTableCN()
                //           + "';";
                // }
                // dataSourceService.executeCreateTableSql(systemDataSource.getId(), sql);

                SaveModelParam param = new SaveModelParam();
                param.setIgnoreTheme(true);
                param.setProjectId(projectId);
                param.setSchema(schema);
                param.setEnName(task.getTargetTable());
                param.setCnName(task.getTargetTableCN());
                param.setLifeCycle(-1);
                param.setThemeId(themeId);
                List<FieldInfoDomain> fieldList =
                    dataSourceService.getFieldList(sourceDsId, sourceTableWithSchema);

                List<SaveModelParam.Field> fields =
                    fieldList.stream()
                        .map(
                            f -> {
                                SaveModelParam.Field f1 =
                                    ConvertUtil.copyProperties(f, SaveModelParam.Field.class);
                                f1.setFieldType(targetManagerInfo.getDbManager().toDbType(f.getDataType()));
                                f1.setFieldDesc(f.getComment());
                                f1.setRequired(!f.isNullable());
                                return f1;
                            })
                        .collect(Collectors.toList());
                param.setFields(fields);
                modelService.saveNewModel(param);

                OfflineSyncDomain offlineSyncDomain = new OfflineSyncDomain();
                offlineSyncDomain.setBatchSize(2000);
                offlineSyncDomain.setChannel(5);
                offlineSyncDomain.setDataReplaceRuleId(5);
                offlineSyncDomain.setDirId(0L);
                offlineSyncDomain.setErrorNumLimit(10);
                offlineSyncDomain.setFetchSize(500);
                offlineSyncDomain.setIsTruncate(0);
                offlineSyncDomain.setSourceDsId(sourceDsId);
                offlineSyncDomain.setSourceDsType(sourceDsType);
                offlineSyncDomain.setSourceTable(sourceTableWithSchema);
                offlineSyncDomain.setTargetDsId(systemDataSource.getId());
                offlineSyncDomain.setTargetDsType(systemDataSource.getDsType());
                offlineSyncDomain.setTargetTable(targetTableWithSchema);
                offlineSyncDomain.setTaskName(
                    schema + "_" + task.getSourceTable() + "_to_" + schema + "_" + targetTableWithSchema);
                OfflineSyncDomain.OfflineSyncSchedule schedule =
                    new OfflineSyncDomain.OfflineSyncSchedule();
                schedule.setCycle(0);
                schedule.setCycleDependence(0);
                schedule.setEffectiveDateEnd(DateUtil.parse("9999-12-30", "yyyy-MM-dd"));
                schedule.setEffectiveDateStart(DateTime.now());
                schedule.setExecPoint("1");
                schedule.setStartTimeHour("00");
                schedule.setStartTimeMin("00");
                offlineSyncDomain.setSchedule(schedule);
                List<OfflineSyncDomain.FieldMapping> mappings = Lists.newArrayList();
                OfflineTaskExistTableMappingQueryDomain sourceQuery =
                    new OfflineTaskExistTableMappingQueryDomain();
                sourceQuery.setTables(Lists.newArrayList(sourceTableWithSchema));
                sourceQuery.setDirection(0);
                sourceQuery.setDsId(sourceDsId);
                OfflineTaskExistTableMappingDomain sourceFields =
                    offlineTaskService.getExistTablesMappings(sourceQuery);
                OfflineTaskExistTableMappingQueryDomain targetQuery =
                    new OfflineTaskExistTableMappingQueryDomain();
                targetQuery.setTables(Lists.newArrayList(targetTableWithSchema));
                targetQuery.setDirection(1);
                targetQuery.setDsId(systemDataSource.getId());
                OfflineTaskExistTableMappingDomain targetFields =
                    offlineTaskService.getExistTablesMappings(targetQuery);
                OfflineTaskExistTableMappingDomain.Table sourceTable = sourceFields.getTables().get(0);
                OfflineTaskExistTableMappingDomain.Table targetTable = targetFields.getTables().get(0);
                sourceTable
                    .getFields()
                    .forEach(
                        source -> {
                            OfflineSyncDomain.FieldMapping fieldMapping =
                                new OfflineSyncDomain.FieldMapping();
                            Optional<OfflineTaskExistTableMappingDomain.Field> any =
                                targetTable.getFields().stream()
                                    .filter(e -> e.getFieldName().equals(source.getFieldName()))
                                    .findAny();
                            if (!any.isPresent()) {
                                throw new BusinessException("同步任务的字段关联异常");
                            }
                            OfflineTaskExistTableMappingDomain.Field target = any.get();
                            fieldMapping.setIsSourceFieldPk(source.isPk() ? 1 : 0);
                            fieldMapping.setSourceField(source.getFieldName());
                            fieldMapping.setSourceFieldType(
                                sourceManagerInfo.getDbManager().toDbType(source.getDataType()));
                            fieldMapping.setIsTargetFieldPk(target.isPk() ? 1 : 0);
                            fieldMapping.setTargetField(target.getFieldName());
                            fieldMapping.setTargetFieldType(
                                targetManagerInfo.getDbManager().toDbType(target.getDataType()));
                            mappings.add(fieldMapping);
                        });
                offlineSyncDomain.setFieldMappings(mappings);
                offlineTaskService.saveOrUpdateOfflineSyncTask(offlineSyncDomain);
                offlineTaskService.run(offlineSyncDomain.getId(), 1);
                //TODO 任务新建保存的时候即自动发布上线运行
                this.publishTask(offlineSyncDomain.getId());
            });
        return true;
    }

    public List<UserResponse> getUserList() {
        List<UserDomain> all = userService.findAll();
        return Convert.toList(UserResponse.class, all);
    }
}
